package algebra.cost;

import kb.DescriptionLogicsKB;
import kb.KBStat;
import reasoner.KBReason;
import utils.exception.CoreReasoningException;
import entities.concept.*;
import entities.role.Role;
//import entities.vocabulary.KBVocabulary;
import algebra.op.*;
import algebra.query.pd.*;
import algebra.query.indexing.CachedIndex;
import algebra.query.indexing.PrimaryIndex;
import algebra.query.indexing.SecondaryIndex;

public class SizeEstimation {
	private final DescriptionLogicsKB kb;
	private final KBStat stat;

	// estimate intermediate result size w.r.t. the given KB
	public SizeEstimation(DescriptionLogicsKB kb) {
		this.kb = kb;
		this.stat = kb == null ? null : kb.getKBStat();
	}

	public double sizeofIndex(CachedIndex ci) {
		if (ci.getDescriptionIndex() != null) {
			// get actual size
			return ci.getDescriptionIndex().getSize();
		} else if (ci instanceof PrimaryIndex) {
			// estimate the size w/o materializing actual indices
			return sizeofOP(new PrimaryOP(ci.asPI().getKB()));
		} else if (ci instanceof SecondaryIndex) {
			return sizeofOP(ci.asSI().getPlanExpression());
		} else {
			System.err.println("Unknown index: " + ci);
			return 0;
		}
	}

	private static double intersectionFactor = 1;
	private static double scanFactor = 0.75;

	// estimate the result size of this op
	public double sizeofOP(AlgebraicOP op) {
		if (op instanceof ConstantOP) {
			return stat == null ? 0 : stat.getConceptSizeInKB(op.asConstantOP()
					.getQueryCocnept());
		} else if (op instanceof JoinOP) {
			// as natural join on a single attribute: ID (i.e., instance name)
			// what we do: select the smaller op as the result
			// the estimation should be order-independent!!
			AlgebraicOP op1 = op.asIntersectionOP().getFirstSubQuery();
			AlgebraicOP op2 = op.asIntersectionOP().getSecondSubQuery();
			return intersectionFactor * Math.min(sizeofOP(op1), sizeofOP(op2));
		} else if (op instanceof PrimaryOP) {
			return stat == null ? 0 : stat.getTotalInstances();
		} else if (op instanceof ProjectionOP) {
			return sizeofOP(op.asProjectionOP().getSubquery());
		} else if (op instanceof SelectionOP) {
			return estimateSelection(op.asSelectionOP());
		} else if (op instanceof ScanOP) {
			ScanOP sp = op.asScanOP();
			double subsize = 0;
			// how selective is the scan C?
			if (sp.getSubquery() instanceof ConstantOP) {
				subsize = sizeofIndex(sp.getSecondaryIndex());
				return subsize
						* conceptSelectivity(sp.getSubquery().asConstantOP()
								.getQueryCocnept());
			} else {
				// a portion of the size of the subquery!
				subsize = sizeofOP(sp.getSubquery());
				return scanFactor * subsize;
			}
		} else {
			System.err.println("Unknonw algebraic op for cost estimation: "
					+ op.getClass());
		}
		return 0;
	}

	// for selection
	public double estimateSelection(SelectionOP sp) {
		double comp = 0;
		Concept c = sp.getSelectionCondition();
		AlgebraicOP sub = sp.getSubquery();
		double subsize = sizeofOP(sub);
		comp = subsize * conceptSelectivity(c);
		if(comp < 1){
			return 1;
		}
		return comp;
	}

	// the selectivity of f, assuming all inputs have f in the description!
	private static final boolean assumeInputHasF = true;

	private double featureEqSelectivtity(Role f) {
		double fmentions = 1;
		if (!assumeInputHasF) {
			fmentions = (1.0 * stat.getFeatureMentionsInKB(f))
					/ stat.getTotalInstances();
		}
		double freq = stat.getFeatureMentionsInKB(f)*1.0 / stat.getFeatureValueSizeInKB(f);
		double frac = 1.0 / stat.getFeatureValueSizeInKB(f);
		return frac * fmentions*freq;
	}

	// estimate the # of R-successors in the result of this op
	private double roleValues(Role r) {
		double frac = (1.0 * kb.getKBStat().getRoleSizeInKB(r))
				/ kb.getKBStat().getTotalInstances();
		return frac;
	}

	// check the selectivity of C in the result of this op
	public double conceptSelectivity(Concept C) {
		double comp = 0;
		double all = stat == null ? 1 : stat.getTotalInstances();
		if (C instanceof PConcept) {
			if(C.isTop()){
				comp = 1;
			}else{
			// estimate # of instances belonging to A
			double nc = stat == null ? 0 : stat.getConceptSizeInKB(C);
			comp = (1.0 * nc) / all;
			}
		} else if (C instanceof DomainConceptEQ) {
			comp = estimateDomainConcept((DomainConcept<?, ?>) C, 0);
		} else if (C instanceof DomainConceptLT) {
			comp = estimateDomainConcept((DomainConcept<?, ?>) C, -1);
		} else if (C instanceof DomainConceptLTEQ) {
			comp = estimateDomainConcept((DomainConcept<?, ?>) C, 1);
		} else if (C instanceof AndConcept) {
			AndConcept ac = (AndConcept) C;
			comp = 1;
			for (Concept d : ac.getConceptSet()) {
				comp *= conceptSelectivity(d);
			}
		} else if (C instanceof OrConcept) {
			OrConcept oc = (OrConcept) C;
			comp = 1;
			for (Concept d : oc.getConceptSet()) {
				comp *= conceptSelectivity(new NotConcept(d));
			}
			comp = 1 - comp;
		} else if (C instanceof ExistsConcept) {
			ExistsConcept ec = (ExistsConcept) C;
			comp = 1;
			comp *= (roleValues(ec.getRole()))
					* conceptSelectivity(ec.getFiller());
		} else if (C instanceof UniversalConcept) {
			// we use: 1 - (someR.~C)
			UniversalConcept uc = (UniversalConcept) C;
			ExistsConcept ec = new ExistsConcept(uc.getRole(), new NotConcept(
					uc.getFiller()));
			comp = 1 - conceptSelectivity(ec);
		} else if (C instanceof Nominal) {
			// nominal interpreted by a single instance
			comp = 1.0 / all;
		} else if (C instanceof NotConcept) {
			NotConcept nc = (NotConcept) C;
			comp = 1 - conceptSelectivity(nc.getConcept());
		} else {
			System.err.println("Unknown concept for cost estimation: "
					+ C.getClass());
			comp = 1;
		}
		// System.err.println("Selectivtity of "+C+" is "+comp);
		return comp >= 1 ? 1 : comp;
	}

	// 0 for =, -1 for <, 1 for <=
	private static double ineqFC = 1.0 / 3;

	private double estimateDomainConcept(DomainConcept<?, ?> dc, int type) {
		double comp = 0;
		Role f = (Role) dc.getRealFeature();
		if (f == null) {
			boolean tau = false;
			try {
				tau = new KBReason().subsumesGuarded(dc, null);
			} catch (CoreReasoningException e) {
				e.printStackTrace();
			}
			if (tau) {
				// tautology
				return 1;
			} else {
				// contradiction
				return 0;
			}
		}
		switch (type) {
		case 0:
			comp = featureEqSelectivtity(f);
			break;
		case -1:
			comp = ineqFC;
			break;
		case 1:
			comp = ineqFC + featureEqSelectivtity(f);
			break;
		}
		return comp >= 1 ? 1 : comp;
	}

	// the size of Lpd, which determines how many reasoning tests needed for a
	// projection
	private final static double rolePdFactor = 1.5;

	public double sizeOfPd(ProjDesc pd, int sizeOfConstants) {
		double comp = 0;
		if(pd instanceof PdConcept){
			comp += 2;
		}else if(pd instanceof PdFeature){
			comp += sizeOfConstants + 1; // an extra special string required
		}else if(pd instanceof PdRole){
			ProjDesc sub = ((PdRole) pd).getSubPd();
			comp += sizeOfPd(sub, sizeOfConstants) * rolePdFactor;
		}else if(pd instanceof PdConjunction){
			comp += sizeOfPd(((PdConjunction) pd).getSubPd1(), sizeOfConstants)
					* sizeOfPd(((PdConjunction) pd).getSubPd2(), sizeOfConstants);
		}
		// exponential many concepts in Lpd
		return Math.pow(comp, 2);
	}

}
